/*
 * Copyright 2008 Nils Adermann
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "couchdb++/JSON/Value.h"
#include "couchdb++/JSON/String.h"

#include <unicode/uchar.h>

using namespace JSON;

Parser::Parser()
{
	values = NULL;
	states = NULL;
	reset();
}

Parser::~Parser()
{
	if (values)
	{
		// make sure there is no unfree'd memory left
		Value* v;
		while ((v = popValue()) != NULL)
		{
			delete v;
		}
		delete values;
	}

	if (states)
	{
		delete states;
	}
}

void Parser::reset()
{
	if (values)
	{
		// make sure there is no unfree'd memory left
		Value* v;
		while ((v = popValue()) != NULL)
		{
			delete v;
		}
		delete values;
	}

	if (states)
	{
		delete states;
	}

	values = new std::stack<Value*>();
	states = new std::stack<ParserState>();

	states->push(STATE_END);
	state = STATE_WAIT_VALUE;

	init = false;
	utf8Length = 0;
	utf8BufferIndex = 0;
	hexBufferIndex = 0;
	lineNumber = 1;
	lineNumberIndex = 0;

	c = U_SENTINEL;
	lastChar = U_SENTINEL;

	numBuffer.clear();
}

#define PARSE_ERROR_SIMPLE(message) \
	throw ParserException(UnicodeString(message), lineNumber)

#define PARSE_ERROR(message, param1, param2) \
	{ \
		UnicodeString u(message); \
		u.append(param1); \
		u.append(param2); \
		throw ParserException(u, lineNumber); \
	}

#define SKIP_WHITESPACE(char) \
	if (char == 0x20 || char == 0x09 || char == 0x0A || char == 0x0D) \
		break;

#define CASE_START_NUMBER \
	case '-': case '+': case '.': case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case 'N': \
		state = STATE_NUMBER; \
		numBuffer.clear(); \
		i--;

#define CASE_LINE_TERMINATOR \
	case '\n': case '\r': case 0x2028: case 0x2029:

void Parser::read(const char* data, size_t len)
{
	Value* value;
	Value* tmp;
	Identifier* key;
	lineNumberIndex = -1;

	/** @todo check encoding (UTF-8, UTF-16LE, UTF-16BE, UTF-32LE, UTF-32BE) **/

	for (size_t i = 0; i < len; ++i)
	{
		lastChar = c;
		c = data[i];

		// non-ascii character needs to be converted to unicode and buffered
		if (!U8_IS_SINGLE(c) || utf8BufferIndex)
		{
			// determine the length using the first character
			if (!utf8BufferIndex)
			{
				utf8Length = U8_LENGTH(c);
			}
			utf8Buffer[utf8BufferIndex++] = c;

			// if we have not yet reached the end of the character read the next byte
			if (utf8BufferIndex < utf8Length)
			{
				continue;
			}
			// otherwise we can parse this character
			else
			{
				utf8BufferIndex = 0;
				int32_t offset = 0;
				U8_NEXT(utf8Buffer, offset, utf8Length, c);

				if (c < 0)
				{
					PARSE_ERROR("Illegal byte sequence, expecting UTF-8: found '", c, "'");
				}
			}
		}

		switch (state)
		{
			case STATE_END:
				SKIP_WHITESPACE(c)

				PARSE_ERROR("Unexpected data after JSON structure: found '", c, "'");
			break;

			case STATE_WAIT_VALUE:
				SKIP_WHITESPACE(c);

				state = STATE_READ_VALUE;
				i--;
			break;

			case STATE_READ_VALUE_SKIP_WHITESPACE_AND_COMMA:
				SKIP_WHITESPACE(c);

				if (c == ',')
				{
					break;
				}

				state = STATE_READ_VALUE;
				i--;
			break;

			case STATE_READ_VALUE_SKIP_WHITESPACE:
				SKIP_WHITESPACE(c);

				state = STATE_READ_VALUE;
				i--;
			break;

			case STATE_READ_VALUE:
				switch (c)
				{
					case '"':
						state = STATE_STRING_DOUBLE_QUOTE;
						pushValue(new String());
					break;
			
					case '\'':
						state = STATE_STRING_SINGLE_QUOTE;
						pushValue(new String());
					break;
			
					case '{':
						state = STATE_OBJECT;
						pushValue(new Object());
						init = true;
					break;
			
					case '[':
						state = STATE_ARRAY;
						pushValue(new Array());
						init = true;
					break;
			
					case 'n':
						state = STATE_NULL_N;
					break;
			
					case 'f':
						state = STATE_BOOL_F;
					break;
			
					case 't':
						state = STATE_BOOL_T;
					break;
			
					CASE_START_NUMBER
					break;
			
					default:
						PARSE_ERROR("Unexpected character data, expecting a value: found '", c, "'");
					break;
				}
			break;

			case STATE_ARRAY:
				SKIP_WHITESPACE(c);
				
				if (!init)
				{
					// get the value
					value = values->top();
					values->pop();
					// put it in the array
					dynamic_cast<Array*>(values->top())->push_back(value);
				}
				
				switch (c)
				{
					case ']':
						state = popState();
					break;

					// there may be any number of commas in an array
					case ',':
						pushState(STATE_ARRAY);
						state = STATE_READ_VALUE_SKIP_WHITESPACE_AND_COMMA;
					break;
				
					default:
						if (init)
						{
							pushState(STATE_ARRAY);
							state = STATE_READ_VALUE;
							i--;
						}
						else
						{
							PARSE_ERROR("Unexpected character data after array element, expecting a comma: found '", c, "'");
						}
					break;
				}

				if (init)
				{
					init = false;
				}
			break;

			// { key  : value, key2 : value2 }
			//  ^            ^              ^^
			case STATE_OBJECT:
				SKIP_WHITESPACE(c);

				if (init)
				{
					init = false;
					state = STATE_OBJECT_KEY;
					i--;
					break;
				}
				else
				{
					// get key and value
					value = popValue();

					tmp = popValue();
					if (tmp->getType() == STRING)
					{
						key = new Identifier(*dynamic_cast<String*>(tmp));
					}
					else if (tmp->getType() == NUMBER)
					{
						key = new Identifier(*dynamic_cast<Number*>(tmp));
					}
					else
					{
						UnicodeString data;
						tmp->toJSON(&data);
						PARSE_ERROR("Unexpected key type, expecting a string or a number: found '", data, "'");
					}
					delete tmp;

					// put it in the object
					dynamic_cast<Object*>(values->top())->insert(*key, value);

					delete key;
				}

				switch (c)
				{
					case '}':
						state = popState();
					break;

					case ',':
						state = STATE_OBJECT_KEY;
					break;

					default:
						PARSE_ERROR("Unexpected character data after object property, expecting a comma: found '", c , "'");
					break;
				}
			break;


			// { key  : value, key2 : value2 }
			//   ^            ^^
			case STATE_OBJECT_KEY:
				SKIP_WHITESPACE(c);
				if (c == ',')
				{
					break;
				}

				// try reading a key, otherwise look for end of object
				switch (c)
				{
					case '$':
					case '_':
						state = STATE_IDENTIFIER_STRING;
						pushValue(new String(c));
						pushState(STATE_OBJECT_VALUE);
					break;

					case '\\':
						state = STATE_IDENTIFIER_STRING_ESCAPE;
						pushValue(new String());
						pushState(STATE_OBJECT_VALUE);
					break;

					case '"':
						state = STATE_STRING_DOUBLE_QUOTE;
						pushValue(new String());
						pushState(STATE_OBJECT_VALUE);
					break;
			
					case '\'':
						state = STATE_STRING_SINGLE_QUOTE;
						pushValue(new String());
						pushState(STATE_OBJECT_VALUE);
					break;

					CASE_START_NUMBER
						pushState(STATE_OBJECT_VALUE);
					break;

					case '}':
						state = popState();
					break;

					default:
						if (isLetter(c))
						{
							state = STATE_IDENTIFIER_STRING;
							pushValue(new String(c));
							pushState(STATE_OBJECT_VALUE);
						}
						else
						{
							PARSE_ERROR("Unexpected character data, expecting an object key: found '", c , "'");
						}
					break;
				}
			break;

			// { key  : value, key2 : value2 }
			//      ^^^            ^^
			case STATE_OBJECT_VALUE:
				SKIP_WHITESPACE(c);

				if (values->top()->getType() != STRING && values->top()->getType() != NUMBER)
				{
					UnicodeString data;
					values->top()->toJSON(&data);
					PARSE_ERROR("Unexpected key type, expecting a string or a number: found '", data , "'");
				}

				if (c != ':')
				{
					PARSE_ERROR("Unexpected character data after object key, expecting a colon: found '", c , "'");
				}
				else
				{
					pushState(STATE_OBJECT);
					state = STATE_READ_VALUE_SKIP_WHITESPACE;
				}
			break;

			case STATE_IDENTIFIER_STRING:
				// assumption: first character was already matched as valid start character

				switch (c)
				{
					case '$':
					case '_':
						topString()->append(c);
					break;

					case '\\':
						state = STATE_IDENTIFIER_STRING_ESCAPE;
					break;

					default:
						if (isLetter(c) || isCombiningMark(c) || isDigit(c) || isConnectorPunctuation(c))
						{
							topString()->append(c);
						}
						else
						{
							state = popState();
							i--;
						}
					break;
				}
			break;

			case STATE_IDENTIFIER_STRING_ESCAPE:
				if (c != 'u')
				{
					PARSE_ERROR("Unexpected escape sequence in object key, expecting a 'u': found '\\", c , "'");
				}
				
				pushState(STATE_IDENTIFIER_STRING);
				state = STATE_STRING_ESCAPE_UNICODE;
			break;

			case STATE_STRING_DOUBLE_QUOTE:
				switch (c)
				{
					case '"':
						state = popState();
					break;

					case '\\':
						state = STATE_STRING_ESCAPE;
						pushState(STATE_STRING_DOUBLE_QUOTE);
					break;

					CASE_LINE_TERMINATOR
						PARSE_ERROR_SIMPLE("Unescaped line terminator in double quoted string");
					break;

					default:
						topString()->append(c);
					break;
				}
			break;

			case STATE_STRING_SINGLE_QUOTE:
				switch (c)
				{
					case '\'':
						state = popState();
					break;

					case '\\':
						state = STATE_STRING_ESCAPE;
						pushState(STATE_STRING_SINGLE_QUOTE);
					break;

					CASE_LINE_TERMINATOR
						PARSE_ERROR_SIMPLE("Unescaped line terminator in single quoted string");
					break;

					default:
						topString()->append(c);
					break;
				}
			break;

			case STATE_STRING_ESCAPE:
				switch (c)
				{
					case 'b':
						topString()->append('\b');
					break;
					case 'f':
						topString()->append('\f');
					break;
					case 'n':
						topString()->append('\n');
					break;
					case 'r':
						topString()->append('\r');
					break;
					case 't':
						topString()->append('\t');
					break;

					case 'u':
						pushState(STATE_STRING_ESCAPE_UNICODE);
					break;

					CASE_LINE_TERMINATOR
						PARSE_ERROR_SIMPLE("Unescaped line terminator in string");
					break;

					case '\'':
					case '\"':
					case '\\':
					case '/':
					case '"':
					case '\'':
						topString()->append(c);
					break;

					default:
						topString()->append('\\');
						topString()->append(c);
					break;
				}

				state = popState();
			break;

			case STATE_STRING_ESCAPE_UNICODE:
			{
				uint8_t value = 0;
				switch (c)
				{
					case '0': value = 0x0; break;
					case '1': value = 0x1; break;
					case '2': value = 0x2; break;
					case '3': value = 0x3; break;
					case '4': value = 0x4; break;
					case '5': value = 0x5; break;
					case '6': value = 0x6; break;
					case '7': value = 0x7; break;
					case '8': value = 0x8; break;
					case '9': value = 0x9; break;
					case 'A': value = 0xA; break;
					case 'B': value = 0xB; break;
					case 'C': value = 0xC; break;
					case 'D': value = 0xD; break;
					case 'E': value = 0xE; break;
					case 'F': value = 0xF; break;
					case 'a': value = 0xA; break;
					case 'b': value = 0xB; break;
					case 'c': value = 0xC; break;
					case 'd': value = 0xD; break;
					case 'e': value = 0xE; break;
					case 'f': value = 0xF; break;

					default:
						PARSE_ERROR("Illegal character in unicode escape sequence: found '", c , "'");
					break;
				}

				// reset the buffer
				if (hexBufferIndex == 0)
				{
					hexBuffer = 0;
				}

				hexBuffer |= value << (4 * (3 - hexBufferIndex));
				hexBufferIndex++;

				if (hexBufferIndex >= 4)
				{
					topString()->append(hexBuffer);
					hexBufferIndex = 0;
					state = popState();
				}
			}
			break;

			case STATE_NUMBER:
				switch (c)
				{
					case '0':
						numBuffer.push_back((char) c);
						state = STATE_NUMBER_DECIMAL_ZERO;
					break;

					// allow a leading -
					case '-':
					case '+':
						if (numBuffer.length() > 1)
						{
							PARSE_ERROR("Illegal leading sign, numbers must only be prefixed with a single sign: found '", c , "'");
						}
						else
						{
							numBuffer.push_back((char) c);
						}
					break;

					case '1':
					case '2':
					case '3':
					case '4':
					case '5':
					case '6':
					case '7':
					case '8':
					case '9':
						numBuffer.push_back((char) c);
						state = STATE_NUMBER_DECIMAL_INTEGER;
					break;

					case '.':
						numBuffer.push_back((char) c);
						state = STATE_NUMBER_DECIMAL_POINT;
					break;

					case 'N':
						state = STATE_NUMBER_N;
					break;

					default:
						PARSE_ERROR("Unexpected character data, expecting number: found '", c , "'");
					break;
				}
			break;

			case STATE_NUMBER_DECIMAL_ZERO:
				switch (c)
				{
					case '.':
						numBuffer.push_back('.');
						state = STATE_NUMBER_DECIMAL_POINT;
					break;

					case 'e':
					case 'E':
						numBuffer.push_back('E');
						state = STATE_NUMBER_EXPONENT;
					break;

					default:
						pushValue(new Number(numBuffer));
						state = popState();
						i--;
					break;
				}
			break;

			case STATE_NUMBER_DECIMAL_INTEGER:
				switch (c)
				{
					case '0':
					case '1':
					case '2':
					case '3':
					case '4':
					case '5':
					case '6':
					case '7':
					case '8':
					case '9':
						numBuffer.push_back((char) c);
					break;

					case '.':
						numBuffer.push_back('.');
						state = STATE_NUMBER_DECIMAL_POINT;
					break;

					case 'e':
					case 'E':
						numBuffer.push_back('E');
						state = STATE_NUMBER_EXPONENT;
					break;

					default:
						pushValue(new Number(numBuffer));
						state = popState();
						i--;
					break;
				}
			break;

			case STATE_NUMBER_DECIMAL_POINT:
				switch (c)
				{
					case '0':
					case '1':
					case '2':
					case '3':
					case '4':
					case '5':
					case '6':
					case '7':
					case '8':
					case '9':
						numBuffer.push_back((char) c);
					break;

					case '.':
						numBuffer.push_back('.');
						state = STATE_NUMBER_DECIMAL_POINT;
					break;

					case 'e':
					case 'E':
						numBuffer.push_back('E');
						state = STATE_NUMBER_EXPONENT;
					break;

					default:
						// a decimal point alone is not a number
						if (numBuffer.length() == 1)
						{
							PARSE_ERROR("Unexpected character data, expecting digit after decimal point: found '", c , "'");
						}
						else
						{
							pushValue(new Number(numBuffer));
							state = popState();
							i--;
						}
					break;
				}
			break;

			case STATE_NUMBER_EXPONENT:
				switch (c)
				{
					case '-':
					case '+':
						numBuffer.push_back((char) c);
						state = STATE_NUMBER_EXPONENT_SIGNED;
					break;

					case '0':
					case '1':
					case '2':
					case '3':
					case '4':
					case '5':
					case '6':
					case '7':
					case '8':
					case '9':
						numBuffer.push_back((char) c);
						state = STATE_NUMBER_EXPONENT_UNSIGNED;
					break;

					default:
						PARSE_ERROR("Unexpected character data, expecting exponent value: found '", c , "'");
					break;
				}
			break;

			case STATE_NUMBER_EXPONENT_SIGNED:
				switch (c)
				{
					case '0':
					case '1':
					case '2':
					case '3':
					case '4':
					case '5':
					case '6':
					case '7':
					case '8':
					case '9':
						numBuffer.push_back((char) c);
						state = STATE_NUMBER_EXPONENT_UNSIGNED;
					break;

					default:
						PARSE_ERROR("Unexpected character data, expecting exponent value: found '", c , "'");
					break;
				}
			break;

			case STATE_NUMBER_EXPONENT_UNSIGNED:
				switch (c)
				{
					case '0':
					case '1':
					case '2':
					case '3':
					case '4':
					case '5':
					case '6':
					case '7':
					case '8':
					case '9':
						numBuffer.push_back((char) c);
					break;

					default:
						pushValue(new Number(numBuffer));
						state = popState();
						i--;
					break;
				}
			break;

			case STATE_NUMBER_N:
				if (c == 'a')
				{
					state = STATE_NUMBER_NA;
				}
				else
				{
					PARSE_ERROR("Unexpected character data, expecting 'a' in NaN: found '", c , "'");
				}
			break;

			case STATE_NUMBER_NA:
				if (c == 'N')
				{
					values->push(new Number(std::string("NaN")));
					state = popState();
				}
				else
				{
					PARSE_ERROR("Unexpected character data, expecting 'N' in NaN: found '", c , "'");
				}
			break;

			case STATE_NULL_N:
				if (c == 'u')
				{
					state = STATE_NULL_NU;
				}
				else
				{
					PARSE_ERROR("Unexpected character data, expecting 'u' in null: found '", c , "'");
				}
			break;

			case STATE_NULL_NU:
				if (c == 'l')
				{
					state = STATE_NULL_NUL;
				}
				else
				{
					PARSE_ERROR("Unexpected character data, expecting 'l' in null: found '", c , "'");
				}
			break;

			case STATE_NULL_NUL:
				if (c == 'l')
				{
					values->push(new Null());
					state = popState();
				}
				else
				{
					PARSE_ERROR("Unexpected character data, expecting 'l' in null: found '", c , "'");
				}
			break;

			case STATE_BOOL_T:
				if (c == 'r')
				{
					state = STATE_BOOL_TR;
				}
				else
				{
					PARSE_ERROR("Unexpected character data, expecting 'r' in true: found '", c , "'");
				}
			break;

			case STATE_BOOL_TR:
				if (c == 'u')
				{
					state = STATE_BOOL_TRU;
				}
				else
				{
					PARSE_ERROR("Unexpected character data, expecting 'u' in true: found '", c , "'");
				}
			break;

			case STATE_BOOL_TRU:
				if (c == 'e')
				{
					values->push(new Bool(true));
					state = popState();
				}
				else
				{
					PARSE_ERROR("Unexpected character data, expecting 'e' in true: found '", c , "'");
				}
			break;

			case STATE_BOOL_F:
				if (c == 'a')
				{
					state = STATE_BOOL_FA;
				}
				else
				{
					PARSE_ERROR("Unexpected character data, expecting 'a' in false: found '", c , "'");
				}
			break;

			case STATE_BOOL_FA:
				if (c == 'l')
				{
					state = STATE_BOOL_FAL;
				}
				else
				{
					PARSE_ERROR("Unexpected character data, expecting 'l' in false: found '", c , "'");
				}
			break;

			case STATE_BOOL_FAL:
				if (c == 's')
				{
					state = STATE_BOOL_FALS;
				}
				else
				{
					PARSE_ERROR("Unexpected character data, expecting 's' in false: found '", c , "'");
				}
			break;

			case STATE_BOOL_FALS:
				if (c == 'e')
				{
					pushValue(new Bool(false));
					state = popState();
				}
				else
				{
					PARSE_ERROR("Unexpected character data, expecting 'e' in false: found '", c , "'");
				}
			break;

			default:
			break;
		}

		// count lines, but don't count the same line break twice
		if ((c == '\r' || (c == '\n' && lastChar != '\r'))  && lineNumberIndex < (int32_t) i)
		{
			lineNumber++;
			lineNumberIndex = i;
		}
	}
}

inline bool Parser::isLetter(UChar32 c)
{
	int8_t type = u_charType(c);
	return (type == U_UPPERCASE_LETTER || type == U_LOWERCASE_LETTER || type == U_TITLECASE_LETTER || type == U_MODIFIER_LETTER || type == U_OTHER_LETTER || type == U_LETTER_NUMBER);
}

inline bool Parser::isCombiningMark(UChar32 c)
{
	int8_t type = u_charType(c);
	return (type == U_NON_SPACING_MARK || type == U_COMBINING_SPACING_MARK);
}

inline bool Parser::isDigit(UChar32 c)
{
	int8_t type = u_charType(c);
	return (type == U_DECIMAL_DIGIT_NUMBER);
}

inline bool Parser::isConnectorPunctuation(UChar32 c)
{
	int8_t type = u_charType(c);
	return (type == U_CONNECTOR_PUNCTUATION);
}

inline void Parser::pushState(ParserState state)
{
	states->push(state);
}

inline ParserState Parser::popState()
{
	if (states->empty())
	{
		PARSE_ERROR_SIMPLE("ParserState stack empty");
	}

	ParserState state = states->top();
	states->pop();
	return state;
}

inline void Parser::pushValue(Value* value)
{
	values->push(value);
}

inline Value* Parser::popValue()
{
	if (values->empty())
	{
		return NULL;
	}

	Value* v = values->top();
	values->pop();
	return v;
}

inline String* Parser::topString()
{
	return dynamic_cast<String*>(values->top());
}

Value* Parser::finish()
{
	// read a space to make sure everything is over
	read(" ", 1);

	if (state != STATE_END)
	{
		PARSE_ERROR_SIMPLE("Incomplete JSON structure");
		return NULL;
	}

	Value* result = popValue();

	// restore initial state
	reset();

	return result;
}

